package gr.uoa.di.thanos.botcraft.gui;

import java.awt.Component;
import java.awt.DisplayMode;
import java.awt.FlowLayout;
import java.awt.GraphicsDevice;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.MessageFormat;
import java.util.Locale;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.prefs.BackingStoreException;

import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.JTabbedPane;
import javax.swing.ListCellRenderer;
import javax.swing.SpinnerNumberModel;
import javax.swing.SwingUtilities;

import org.lwjgl.opencl.CLDevice;
import org.lwjgl.opencl.CLPlatform;

import static org.lwjgl.opencl.CL10.*;
import gr.uoa.di.thanos.botcraft.Configuration;

public class Settings extends JFrame implements ActionListener, Runnable {
	private static final long serialVersionUID = 1L;
	private static final int MIN_FRAME_RATE = 1;
	private static final int MAX_FRAME_RATE = 60;
	private static final String FULL_PROFILE = "FULL_PROFILE";
	private static final Logger LOGGER = Logger.getLogger(Settings.class.getName());
	
	private final Configuration configuration;
	private final JComboBox<Locale> locales;
	private final JComboBox<GraphicsDevice> screens;
	private final JComboBox<DisplayMode> displayModes;
	private final JSpinner frameRate;
	private final JCheckBox displayFullScreen;
	private final JComboBox<CLPlatform> clPlatforms;
	private final JComboBox<CLDevice> clDevices;
	private final JCheckBox shareGlClContext;
	private final JButton save;
	private final JButton cancel;
	
	public static void main(final String[] arguments) {
		final Settings settings = new Settings(new Configuration());
		settings.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		SwingUtilities.invokeLater(settings);
	}
	
	public Settings(final Configuration configuration) {
		this.configuration = configuration;
		setTitle(configuration.getResourceBundle().getString("settings"));
		setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
		setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));
		final JTabbedPane tabbedPane = new JTabbedPane();
		final JPanel general = new JPanel();
		general.setLayout(new GridLayout(0, 2));
		general.add(new JLabel(configuration.getResourceBundle().getString("locale"), JLabel.RIGHT));
		locales = new JComboBox<Locale>();
		for (Locale locale : configuration.getLocales())
			locales.addItem(locale);
		locales.setSelectedItem(configuration.getLocale());
		locales.setEnabled(locales.getItemCount() > 1);
		locales.setRenderer(new ListCellRenderer<Locale>() {
			@Override
			public Component getListCellRendererComponent(final JList<? extends Locale> locales, final Locale locale, final int index, final boolean selected, final boolean focused) {
				return new JLabel(locale.getDisplayLanguage(locale));
			}
		});
		general.add(locales);
		tabbedPane.addTab(configuration.getResourceBundle().getString("general"), general);
		final JPanel video = new JPanel();
		video.setLayout(new GridLayout(0, 2));
		video.add(new JLabel(configuration.getResourceBundle().getString("screen"), JLabel.RIGHT));
		screens = new JComboBox<GraphicsDevice>();		
		for (GraphicsDevice screen : configuration.getScreens())
			screens.addItem(screen);
		screens.setSelectedItem(configuration.getScreen());
		screens.setEnabled(screens.getItemCount() > 1);
		screens.setRenderer(new ListCellRenderer<GraphicsDevice>() {
			@Override
			public Component getListCellRendererComponent(final JList<? extends GraphicsDevice> screens, final GraphicsDevice screen, final int index, final boolean selected, final boolean focused) {
				return new JLabel(screen.getIDstring());
			}
		});
		screens.addActionListener(this);
		video.add(screens);	
		video.add(new JLabel(configuration.getResourceBundle().getString("displayMode"), JLabel.RIGHT));
		displayModes = new JComboBox<DisplayMode>();
		for (DisplayMode displayMode : configuration.getDisplayModes(configuration.getScreen()))
			displayModes.addItem(displayMode);
		displayModes.setSelectedItem(configuration.getDisplayMode());
		displayModes.setEnabled(displayModes.getItemCount() > 1);
		displayModes.setRenderer(new ListCellRenderer<DisplayMode>() {
			@Override
			public Component getListCellRendererComponent(final JList<? extends DisplayMode> displayModes, final DisplayMode displayMode, final int index, final boolean selected, final boolean focused) {
				return new JLabel((displayMode.getBitDepth() == DisplayMode.BIT_DEPTH_MULTI) ? MessageFormat.format(configuration.getResourceBundle().getString("_displayModeNoBitDepthFormatString"), displayMode.getWidth(), displayMode.getHeight()) : MessageFormat.format(configuration.getResourceBundle().getString("_displayModeWithBitDepthFormatString"), displayMode.getWidth(), displayMode.getHeight(), displayMode.getBitDepth()));
			}
		});		
		video.add(displayModes);
		video.add(new JLabel(configuration.getResourceBundle().getString("frameRate_Hz_"), JLabel.RIGHT));
		frameRate = new JSpinner(new SpinnerNumberModel(Configuration.DEFAULT_FRAME_RATE, MIN_FRAME_RATE, MAX_FRAME_RATE, 1));
		frameRate.setValue(configuration.getFrameRate());
		video.add(frameRate);
		video.add(new JLabel(configuration.getResourceBundle().getString("displayFullScreen"), JLabel.RIGHT));
		displayFullScreen = new JCheckBox();
		displayFullScreen.setSelected(configuration.isDisplayFullScreen());
		video.add(displayFullScreen);
		tabbedPane.addTab(configuration.getResourceBundle().getString("video"), video);
		final JPanel advanced = new JPanel();
		advanced.setLayout(new GridLayout(0, 2));
		advanced.add(new JLabel(configuration.getResourceBundle().getString("openCLPlatform"), JLabel.RIGHT));
		clPlatforms = new JComboBox<CLPlatform>();
		for (CLPlatform clPlatform : configuration.getOpenCLPlatforms())
			clPlatforms.addItem(clPlatform);
		clPlatforms.setSelectedItem(configuration.getOpenCLPlatform());
		clPlatforms.setEnabled(clPlatforms.getItemCount() > 1);
		clPlatforms.setRenderer(new ListCellRenderer<CLPlatform>() {
			@Override
			public Component getListCellRendererComponent(final JList<? extends CLPlatform> clPlatforms, final CLPlatform clPlatform, final int index, final boolean selected, final boolean focused) {
				return new JLabel(MessageFormat.format(configuration.getResourceBundle().getString("_openCLPlatformFormatString"), clPlatform.getInfoString(CL_PLATFORM_NAME), clPlatform.getInfoString(CL_PLATFORM_VERSION), configuration.getResourceBundle().getString(clPlatform.getInfoString(CL_PLATFORM_PROFILE).equals(FULL_PROFILE) ? "fullProfile" : "embeddedProfile"), clPlatform.getInfoString(CL_PLATFORM_VENDOR)));
			}
		});
		clPlatforms.addActionListener(this);
		advanced.add(clPlatforms);	
		advanced.add(new JLabel(configuration.getResourceBundle().getString("openCLDevice"), JLabel.RIGHT));
		clDevices = new JComboBox<CLDevice>();
		for (CLDevice clDevice : configuration.getOpenCLDevices(configuration.getOpenCLPlatform()))
			clDevices.addItem(clDevice);
		clDevices.setSelectedItem(configuration.getOpenCLDevice());
		clDevices.setEnabled(clDevices.getItemCount() > 1);
		clDevices.setRenderer(new ListCellRenderer<CLDevice>() {
			@Override
			public Component getListCellRendererComponent(final JList<? extends CLDevice> clDevices, final CLDevice clDevice, final int index, final boolean selected, final boolean focused) {
				String type = null;
				switch (clDevice.getInfoInt(CL_DEVICE_TYPE)) {
				case CL_DEVICE_TYPE_ACCELERATOR:
					type = "accelerator";
					break;
				case CL_DEVICE_TYPE_CPU:
					type = "cpu";
					break;
				case CL_DEVICE_TYPE_GPU:
					type = "gpu";
				}
				return new JLabel(MessageFormat.format(configuration.getResourceBundle().getString("_openCLDeviceFormatString"), clDevice.getInfoString(CL_DEVICE_NAME), configuration.getResourceBundle().getString(type), clDevice.getInfoInt(CL_DEVICE_MAX_CLOCK_FREQUENCY), clDevice.getInfoString(CL_DEVICE_VENDOR)));
			}
		});		
		advanced.add(clDevices);
		advanced.add(new JLabel(configuration.getResourceBundle().getString("shareContextBetweenOpenGlAndOpenCl"), JLabel.RIGHT));
		shareGlClContext = new JCheckBox();
		shareGlClContext.setSelected(configuration.isShareGlClContext());
		advanced.add(shareGlClContext);
		tabbedPane.addTab(configuration.getResourceBundle().getString("advanced"), advanced);
		add(tabbedPane);
		final JPanel buttons = new JPanel();
		buttons.setLayout(new FlowLayout());
		save = new JButton(configuration.getResourceBundle().getString("save"));
		save.addActionListener(this);
		buttons.add(save);
		cancel = new JButton(configuration.getResourceBundle().getString("cancel"));
		cancel.addActionListener(this);
		buttons.add(cancel);
		add(buttons);
		pack();
	}
	
	@Override
	public void actionPerformed(final ActionEvent actionEvent) {
		if (actionEvent.getSource() == screens) {
			final GraphicsDevice screen = screens.getItemAt(screens.getSelectedIndex());
			if (screen != null) {
				displayModes.removeAllItems();
				for (DisplayMode displayMode : configuration.getDisplayModes(screen))
					displayModes.addItem(displayMode);
				displayModes.setSelectedItem(configuration.getDisplayMode());
				displayModes.setEnabled(displayModes.getItemCount() > 1);
			}
		} else if (actionEvent.getSource() == clPlatforms) {
			final CLPlatform clPlatform = clPlatforms.getItemAt(clPlatforms.getSelectedIndex());
			if (clPlatform != null) {
				clDevices.removeAllItems();
				for (CLDevice clDevice : configuration.getOpenCLDevices(clPlatform))
					clDevices.addItem(clDevice);
				clDevices.setSelectedItem(configuration.getOpenCLDevice());
				clDevices.setEnabled(clDevices.getItemCount() > 1);
			}
		} else if (actionEvent.getSource() == save) {
			try {
				final Locale locale = locales.getItemAt(locales.getSelectedIndex());
				if (locale != null)
					configuration.setLocale(locale);
				final GraphicsDevice screen = screens.getItemAt(screens.getSelectedIndex());
				if (screen != null)
					configuration.setScreen(screen);
				final DisplayMode displayMode = displayModes.getItemAt(displayModes.getSelectedIndex());
				if (displayMode != null)
					configuration.setDisplayMode(displayMode);
				configuration.setFrameRate((Integer) frameRate.getValue());
				configuration.setDisplayFullScreen(displayFullScreen.isSelected());
				final CLPlatform clPlatform = clPlatforms.getItemAt(clPlatforms.getSelectedIndex());
				if (clPlatform != null)
					configuration.setOpenCLPlatform(clPlatform);
				final CLDevice clDevice = clDevices.getItemAt(clDevices.getSelectedIndex());
				if (clDevice != null)
					configuration.setOpenCLDevice(clDevice);
				configuration.setShareGlClContext(shareGlClContext.isSelected());
			} catch (final BackingStoreException e) {
				LOGGER.log(Level.WARNING, "Error saving settings", e);
				JOptionPane.showMessageDialog(Settings.this, configuration.getResourceBundle().getString("errorSavingSettings"), e.getClass().getName(), JOptionPane.WARNING_MESSAGE);
			}
			dispose();
		} else if (actionEvent.getSource() == cancel) {
			final Locale locale = locales.getItemAt(locales.getSelectedIndex());
			final GraphicsDevice screen = screens.getItemAt(screens.getSelectedIndex());
			final DisplayMode displayMode = displayModes.getItemAt(displayModes.getSelectedIndex());
			final CLPlatform clPlatform = clPlatforms.getItemAt(clPlatforms.getSelectedIndex());
			final CLDevice clDevice = clDevices.getItemAt(clDevices.getSelectedIndex());
			if (((locale != null) && (!locale.equals(configuration.getLocale()))) || ((screen != null) && (!screen.equals(configuration.getScreen()))) || ((displayMode != null) && (!displayMode.equals(configuration.getDisplayMode()))) || (((Integer) frameRate.getValue()) != configuration.getFrameRate()) || (displayFullScreen.isSelected() != configuration.isDisplayFullScreen()) || ((clPlatform != null) && (!clPlatform.equals(configuration.getOpenCLPlatform()))) || ((clDevice != null) && (!clDevice.equals(configuration.getOpenCLDevice()))) || (shareGlClContext.isSelected() != configuration.isShareGlClContext())) {
				switch (JOptionPane.showConfirmDialog(Settings.this, configuration.getResourceBundle().getString("wouldYouLikeToSaveYourChangesBeforeClosingThisWindow_"), configuration.getResourceBundle().getString("saveChanges"), JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE)) {
				case JOptionPane.YES_OPTION:
					try {
						if (locale != null)
							configuration.setLocale(locale);
						if (screen != null)
							configuration.setScreen(screen);
						if (displayMode != null)
							configuration.setDisplayMode(displayMode);
						configuration.setFrameRate((Integer) frameRate.getValue());
						configuration.setDisplayFullScreen(displayFullScreen.isSelected());
						if (clPlatform != null)
							configuration.setOpenCLPlatform(clPlatform);
						if (clDevice != null)
							configuration.setOpenCLDevice(clDevice);
						configuration.setShareGlClContext(shareGlClContext.isSelected());
					} catch (final BackingStoreException e) {
						LOGGER.log(Level.WARNING, "Error saving settings", e);
						JOptionPane.showMessageDialog(Settings.this, configuration.getResourceBundle().getString("errorSavingSettings"), e.getClass().getName(), JOptionPane.WARNING_MESSAGE);
					}
					dispose();
					break;
				case JOptionPane.NO_OPTION:
					dispose();
				}
			} else
				dispose();
		}
	}
	
	@Override
	public void run() {
		setVisible(true);
		requestFocus();
		locales.requestFocusInWindow();
	}
}
